package com.ccExcelUtil.util;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.read.metadata.ReadSheet;
import com.ccExcelUtil.validator.ValidatorUtils;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;

import static com.ccExcelUtil.constant.GlobalConstant.*;

@SuppressWarnings("unchecked")
public class ExcelUtils {

    private static Logger logger = LoggerFactory.getLogger(ExcelUtils.class);


    public static <T> Map<String, Object> readData(InputStream inputStream, Class<T> t) {
        Map<String, Object> map = new HashMap<>(3);
        Map<Integer, Map<String, Object>> excelMap = new HashMap<>();
        Map<Integer, Object> valueMap = new HashMap<>();
        SXSSFWorkbook sxssfWorkbook = new SXSSFWorkbook();

        final boolean[] flag = {false};

        EasyExcelFactory.read(inputStream, t, new ReadListener<T>() {
            int index = 0;

            @Override
            public void invoke(Object o, AnalysisContext analysisContext) {
                logger.info("解析到一条数据:{}", o);
                if (index == 0) {
                    excelMap.put(index, new HashMap<>());
                } else {
                    // 设置请求头
                    Map<String, Object> map = ValidatorUtils.getValidatedObject(o);
                    if (!map.isEmpty()) {
                        flag[0] = true;
                    }
                    excelMap.put(index, map);
                }
                valueMap.put(index, o);
                index++;
            }

            @Override
            public void doAfterAllAnalysed(AnalysisContext analysisContext) {
                logger.info("index={}", index);
                SXSSFSheet sheet = sxssfWorkbook.createSheet();
                setExcelData(sxssfWorkbook, sheet, valueMap, excelMap);
            }
        }).sheet().headRowNumber(0).doRead();

        List<Object> values = new ArrayList<>();
        valueMap.forEach((k, v) -> values.add(v));
        map.put(RES, flag[0]);
        map.put(SXSSF_WORKBOOK, sxssfWorkbook);
        map.put(DATA, values);
        return map;
    }

    public static <T> Map<String, Object> readData(InputStream inputStream, Class<T> t, int row, InputStream templateInputStream) {
        Map<String, Object> map = new HashMap<>(3);
        Map<Integer, Map<String, Object>> excelMap = new HashMap<>();
        Map<Integer, Object> valueMap = new HashMap<>();
        final SXSSFWorkbook[] sxssfWorkbook = {null};
        final boolean[] flag = {false};

        EasyExcelFactory.read(inputStream, t, new ReadListener<T>() {
            int index = 0;

            @Override
            public void invoke(Object o, AnalysisContext analysisContext) {
                logger.info("解析到一条数据:{}", o);
                if (index >= row) {
                    Map<String, Object> map = ValidatorUtils.getValidatedObject(o);
                    if (!map.isEmpty()) {
                        flag[0] = true;
                    }
                    excelMap.put(index, map);
                    valueMap.put(index, o);
                }
                index++;
            }

            @Override
            public void doAfterAllAnalysed(AnalysisContext analysisContext) {
                // 往模版中设置值
                SXSSFSheet sheet;
                if (templateInputStream == null) {
                    sxssfWorkbook[0] = new SXSSFWorkbook();
                    sheet = sxssfWorkbook[0].createSheet();
                } else {
                    try {
                        XSSFWorkbook xwb = new XSSFWorkbook(templateInputStream);
                        sxssfWorkbook[0] = new SXSSFWorkbook(xwb);
                        sheet = sxssfWorkbook[0].getSheetAt(0);
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                }
                setExcelData(sxssfWorkbook[0], sheet, valueMap, excelMap);
            }
        }).sheet().headRowNumber(0).doRead();

        List<Object> values = new ArrayList<>();
        valueMap.forEach((k, v) -> values.add(v));
        map.put(RES, flag[0]);
        map.put(SXSSF_WORKBOOK, sxssfWorkbook[0]);
        map.put(DATA, values);
        return map;
    }


    public static Map<String, Object> readSheet(InputStream inputStream, Map<Integer, Class<?>> map, SXSSFWorkbook sxssfWorkbook) {
        Map<String, Object> resultMap = new HashMap<>(2);
        List<Class<?>> classes = new ArrayList<>();
        map.forEach((k, v) -> {
            classes.add(v);
        });
        ByteArrayOutputStream baos = ObjectUtils.cloneInputStream(inputStream);
        // 复制两个新的流
        InputStream cloneIs1 = new ByteArrayInputStream(baos.toByteArray());
        InputStream cloneIs2 = new ByteArrayInputStream(baos.toByteArray());
        // 读取所有sheet的值供后面使用（k,v）->(sheet,dataList)
        Map<Integer, List<?>> multiSheetData = getMultiSheetData(cloneIs1, classes);
        ContextHolder.setDataMap(multiSheetData);

        final boolean[] flag = {false};
        Map<Integer, List<Object>> dataMap = new HashMap<>();
        try (ExcelReader excelReader = EasyExcelFactory.read(cloneIs2).build()) {
            List<ReadSheet> readSheetList = new ArrayList<>();
            map.keySet().forEach(item -> {
                Class<?> aClass = map.get(item);
                Map<Integer, Map<String, Object>> excelMap = new HashMap<>();
                Map<Integer, Object> valueMap = new HashMap<>();
                ReadSheet readSheet = EasyExcel.readSheet(item).head(aClass).registerReadListener(new ReadListener<Object>() {
                    int index = 0;

                    @Override
                    public void invoke(Object o, AnalysisContext analysisContext) {
                        if (index == 0) {
                            excelMap.put(index, new HashMap<>());
                        } else {
                            Map<String, Object> map = ValidatorUtils.getValidatedObject(o);
                            if (!map.isEmpty()) {
                                flag[0] = true;
                            }
                            excelMap.put(index, map);
                        }
                        valueMap.put(index, o);
                        index++;
                    }

                    @Override
                    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
                        String sheetName = "sheet" + item;
                        List<Object> values = new ArrayList<>();
                        valueMap.forEach((k, v) -> values.add(v));
                        dataMap.put(item, values);
                        SXSSFSheet sheet = sxssfWorkbook.createSheet(sheetName);
                        setExcelData(sxssfWorkbook, sheet, valueMap, excelMap);
                    }
                }).headRowNumber(0).build();
                readSheetList.add(readSheet);
            });
            excelReader.read(readSheetList);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        resultMap.put(RES, flag[0]);
        resultMap.put(SXSSF_WORKBOOK, sxssfWorkbook);
        resultMap.put(DATA, dataMap);
        return resultMap;
    }


    public static Map<String, Object> readSheet(InputStream inputStream, Map<Integer, Class<?>> map, Integer[] rows, SXSSFWorkbook sxssfWorkbook) {
        Map<String, Object> resultMap = new HashMap<>(2);
        List<Class<?>> classes = new ArrayList<>();
        map.forEach((k, v) -> {
            classes.add(v);
        });
        ByteArrayOutputStream baos = ObjectUtils.cloneInputStream(inputStream);
        // 复制两个新的流
        InputStream cloneIs1 = new ByteArrayInputStream(baos.toByteArray());
        InputStream cloneIs2 = new ByteArrayInputStream(baos.toByteArray());
        // 读取所有sheet的值供后面使用（k,v）->(sheet,dataList)
        Map<Integer, List<?>> multiSheetData = getMultiSheetData(cloneIs1, classes);
        ContextHolder.setDataMap(multiSheetData);
        final boolean[] flag = {false};
        Map<Integer, List<Object>> dataMap = new HashMap<>();
        try (ExcelReader excelReader = EasyExcelFactory.read(cloneIs2).build()) {
            List<ReadSheet> readSheetList = new ArrayList<>();
            map.keySet().forEach(item -> {
                Class<?> aClass = map.get(item);
                int row = rows[item];
                Map<Integer, Map<String, Object>> excelMap = new HashMap<>();
                Map<Integer, Object> valueMap = new HashMap<>();
                ReadSheet readSheet = EasyExcel.readSheet(item).head(aClass).registerReadListener(new ReadListener<Object>() {
                    int index = 0;

                    @Override
                    public void invoke(Object o, AnalysisContext analysisContext) {
                        if (index >= row) {
                            Map<String, Object> map = ValidatorUtils.getValidatedObject(o);
                            if (!map.isEmpty()) {
                                flag[0] = true;
                            }
                            excelMap.put(index, map);
                            valueMap.put(index, o);
                        }
                        index++;
                    }

                    @Override
                    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
                        SXSSFSheet sheet = sxssfWorkbook.getSheetAt(item);
                        setExcelData(sxssfWorkbook, sheet, valueMap, excelMap);
                        List<Object> values = new ArrayList<>();
                        valueMap.forEach((k, v) -> values.add(v));
                        dataMap.put(item, values);
                    }
                }).headRowNumber(0).build();
                readSheetList.add(readSheet);
            });
            excelReader.read(readSheetList);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        resultMap.put(RES, flag[0]);
        resultMap.put(SXSSF_WORKBOOK, sxssfWorkbook);
        resultMap.put(DATA, dataMap);
        return resultMap;
    }

    public static Map<Integer, List<?>> getMultiSheetData(InputStream inputStream, List<Class<?>> classes) {
        Map<Integer, Class<?>> map = new HashMap<>();
        for (int i = 0; i < classes.size(); i++) {
            map.put(i, classes.get(i));
        }
        Map<Integer, List<?>> dataMap = new HashMap<>(16);
        try (ExcelReader excelReader = EasyExcelFactory.read(inputStream).build()) {
            List<ReadSheet> readSheetList = new ArrayList<>();
            map.keySet().forEach(item -> {
                Class<?> aClass = map.get(item);
                List<Object> dataList = new ArrayList<>();
                ReadSheet readSheet = EasyExcelFactory.readSheet(item).head(aClass).registerReadListener(new ReadListener<Object>() {
                    @Override
                    public void invoke(Object o, AnalysisContext analysisContext) {
                        dataList.add(o);
                    }

                    @Override
                    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
                        dataMap.put(item, dataList);
                    }
                }).build();
                readSheetList.add(readSheet);
            });
            excelReader.read(readSheetList);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return dataMap;
    }

    public static void setExcelData(SXSSFWorkbook sxssfWorkbook, Sheet sheet, Map<Integer, Object> valueMap, Map<Integer, Map<String, Object>> excelMap) {
        CellStyle cellStyle = sxssfWorkbook.createCellStyle();
        Font font = sxssfWorkbook.createFont();
        cellStyle.setFont(font);
        valueMap.keySet().forEach(item -> {
            Row row = sheet.createRow(item);
            Object o = valueMap.get(item);
            Field[] declaredFields = o.getClass().getDeclaredFields();
            Map<String, Object> errorMap = excelMap.get(item);
            Map<Integer, String> fieldNameMap = new TreeMap<>((Comparator.comparingInt(o2 -> o2)));
            for (Field field : declaredFields) {
                ExcelProperty annotation = field.getAnnotation(ExcelProperty.class);
                if (annotation != null) {
                    fieldNameMap.put(annotation.index(), field.getName());
                }
            }
            if (!fieldNameMap.isEmpty() && fieldNameMap.size() != declaredFields.length) {
                throw new RuntimeException();
            }
            if (fieldNameMap.isEmpty()) {
                for (int i = 0; i < declaredFields.length; i++) {
                    Field declaredField = declaredFields[i];
                    String fieldName = declaredField.getName();
                    setCellValue(cellStyle, font, row, o, errorMap, i, fieldName);
                }
            } else {
                for (int i = 0; i < fieldNameMap.size(); i++) {
                    String fieldName = fieldNameMap.get(i);
                    setCellValue(cellStyle, font, row, o, errorMap, i, fieldName);
                }
            }
        });
    }

    /**
     * 设置单元格的值
     *
     * @param cellStyle
     * @param font
     * @param row
     * @param o
     * @param errorMap
     * @param i
     * @param fieldName
     */
    public static void setCellValue(CellStyle cellStyle, Font font, Row row, Object o, Map<String, Object> errorMap, int i, String fieldName) {
        Cell cell = row.createCell(i);
        String content = (String) ObjectUtils.getValue(o, fieldName);
        if (errorMap.containsKey(fieldName)) {
            font.setColor(IndexedColors.RED.getIndex());
            cell.setCellStyle(cellStyle);
            cell.setCellValue(errorMap.get(fieldName) + content);
        } else {
            cell.setCellValue(content);
        }
    }


    /**
     * 校验成功返回数据，校验失败返回的数据为空
     *
     * @param toFilePath
     * @param map
     * @param <T>
     * @return
     */
    public static <T> List<T> writeToXssList(String toFilePath, Map<String, Object> map) {
        boolean res = (Boolean) map.get(RES);
        if (res) {
            SXSSFWorkbook sxssfWorkbook = (SXSSFWorkbook) map.get(SXSSF_WORKBOOK);
            OutputStream outputStream;
            try {
                outputStream = Files.newOutputStream(Paths.get(toFilePath));
                sxssfWorkbook.write(outputStream);
                outputStream.close();
                return Collections.emptyList();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        return (List<T>) map.get(DATA);
    }

    /**
     * 校验成功返回数据，校验失败返回空
     *
     * @param toFilePath
     * @param map
     * @return
     */
    public static Map<Integer, List<Object>> writeToXssMap(String toFilePath, Map<String, Object> map) {
        boolean res = (Boolean) map.get(RES);
        if (res) {
            SXSSFWorkbook sxssfWorkbook = (SXSSFWorkbook) map.get(SXSSF_WORKBOOK);
            OutputStream outputStream;
            try {
                outputStream = Files.newOutputStream(Paths.get(toFilePath));
                sxssfWorkbook.write(outputStream);
                outputStream.close();
                return Collections.emptyMap();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        return (Map<Integer, List<Object>>) map.get(DATA);
    }

    public static ByteArrayInputStream writeToWeb(Map<String, Object> map) {
        boolean res = (Boolean) map.get(RES);
        if (res) {
            SXSSFWorkbook sxssfWorkbook = (SXSSFWorkbook) map.get(SXSSF_WORKBOOK);
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            try {
                sxssfWorkbook.write(bos);
                return new ByteArrayInputStream(bos.toByteArray());
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        return null;
    }

    public static <T> List<T> getData(InputStream inputStream, Class<T> t) {
        List<T> dataList = new ArrayList<>();
        EasyExcelFactory.read(inputStream, t, new ReadListener<T>() {
            @Override
            public void invoke(Object o, AnalysisContext analysisContext) {
                dataList.add((T) o);
            }

            @Override
            public void doAfterAllAnalysed(AnalysisContext analysisContext) {
                logger.info("解析数据完成");
            }
        }).sheet().doRead();
        return dataList;
    }

}
